import java.io.IOException;
import java.io.Serializable;

import mobilefeel.AndroidDevice;
import mobilefeel.DeviceListener;
import mobilefeel.MobileFeel;
import mobilefeel.accelerometer.AccelerometerListener;
import mobilefeel.dto.AccelerometerEvent;
import mobilefeel.dto.TouchEvent;
import mobilefeel.touchscreen.OnTouchListener;
import mobilefeel.touchscreen.View;
import mobilefeel.touchscreen.ViewListener;

import org.flypad.joystick.Joystick;
import org.flypad.joystick.JoystickException;



public class GamePad implements DeviceListener, OnTouchListener, ViewListener, AccelerometerListener{
	
	private String modo;
	private Joystick joystick;
	private int updateInterval;
	private long[] timestampOn;
	private long[] timestampOff;
	private float factorX;
	private float factorY;
	private float limiteX;
	private float limiteY;
	private int anguloLimite;
	private float factorAngular;
	private double anguloAnterior;
	private float deadzoneX;
	private float deadzoneY;
	private Thread updateThread;
	private View button1, button2, acelerador, freno;
	
	public GamePad(){
		MobileFeel android =  new MobileFeel();
		modo = "";
		timestampOn = new long[] {0, 0};
		timestampOff = new long[] {0, 0};
		anguloAnterior = 0;
		updateInterval = 25;
		setDeadzones(0.3F, 0.25F);
		setLimites(6, 5);
		setAnguloLimite(160);
		android.addDeviceListener(this);
		try {
			android.listen(51460);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void setLimites(float x, float y) {
		if (x > 0 && y > 0) {
			limiteX = x;
			limiteY = y;
			factorX = (16384/limiteX);
			factorY = (16384/limiteY);
		}
	}
	
	public void setAnguloLimite(int angulo) {
		if (angulo > 0) {
			anguloLimite = angulo;
			factorAngular = (16384/anguloLimite);
		}
	}
	
	public void setDeadzones(float x, float y) {
		if (x >= 0 && y >= 0) {
			deadzoneX = x;
			deadzoneY = y;
		}
	}

	@Override
	public void onViewRegistered(AndroidDevice device, View view) {
		if (view.getName().equals("acelerador"))
			acelerador = view;
		else if (view.getName().equals("freno"))
			freno = view;
		else if (view.getName().equals("button1"))
			button1 = view;
		else if (view.getName().equals("button2"))
			button2 = view;
	}

	@Override
	public void onDeviceConnected(AndroidDevice device) {
		System.out.println("DEVICE: " + device.getModel());
		
		device.getTouchScreen().addViewListener(this);
		try {
			joystick = new Joystick();
			
			updateThread = new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						while (!Thread.interrupted()) {
							joystick.send();
							Thread.sleep(updateInterval);
						}
					} catch (JoystickException e) {
						e.printStackTrace();
					} catch (InterruptedException e) {
						System.out.println("Thread interrupted");
					}
				}
			});
			updateThread.start();
		} catch (JoystickException e) {
			e.printStackTrace();
		}
	
	}

	@Override
	public void onDeviceDisconnected(AndroidDevice device) {
		try {
			updateThread.interrupt();
			joystick.close();
		} catch (JoystickException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onTouch(View v, TouchEvent e) {
		//try {
			int boton = -1;
			if(v.getName().equals("button1") || v.getName().equals("acelerador"))
				boton = 0;
			else if (v.getName().equals("button2") || v.getName().equals("freno"))
				boton = 1;
			
			//System.out.println(v.getName() + "pressed: " + e.isViewPressed());
			if(/*e.isViewPressed() && */joystick.digital[boton] != Joystick.DIGITAL_ON && e.getAction() == TouchEvent.ACTION_DOWN){
				System.out.println("e.getEventTime() - timestampOff[boton] = " + (e.getEventTime() - timestampOff[boton]));
				//if((e.getEventTime() - timestampOff[boton]) < 200)
					//Thread.sleep(200 - (e.getEventTime() - timestampOff[boton]));
				joystick.digital[boton] = Joystick.DIGITAL_ON;
				//joystick.send();
				timestampOn[boton] = e.getEventTime();
			}
			else if(/*!e.isViewPressed() && */joystick.digital[boton] != Joystick.DIGITAL_OFF && (e.getAction() == TouchEvent.ACTION_UP || e.getAction() == TouchEvent.ACTION_OUTSIDE || e.getAction() == TouchEvent.ACTION_CANCEL)){
				System.out.println("e.getEventTime() - timestampOn[boton] = " + (e.getEventTime() - timestampOn[boton]));
				//if((e.getEventTime() - timestampOn[boton]) < 100)
					//Thread.sleep(100 - (e.getEventTime() - timestampOn[boton]));
				joystick.digital[boton] = Joystick.DIGITAL_OFF;
				//joystick.send();
				timestampOff[boton] = e.getEventTime();
			}
		/*} catch (JoystickException e1) {
			e1.printStackTrace();
		} /*catch (InterruptedException e2) {
			e2.printStackTrace();
		}*/
	}
	
	
	public static void main (String[] args){
		new GamePad();
	}

	@Override
	public void onSensorChanged(AndroidDevice device, AccelerometerEvent event) {
		float x = event.getY();
		float y = event.getX();
		
		if (x > -deadzoneX && x < deadzoneX)
			x = 0;
		else if (x < -deadzoneX)
			x += deadzoneX;
		else if (x > deadzoneX)
			x -= deadzoneX;
		
		if (y > -deadzoneY && y < deadzoneY)
			y = 0;
		else if (y < -deadzoneY)
			y += deadzoneY;
		else if (y > deadzoneY)
			y -= deadzoneY;

		if (modo.equals("gamepad")) {			
			x = x * factorX + Joystick.ANALOG_MID;
			y = y * factorY + Joystick.ANALOG_MID;
				
			if (x < Joystick.ANALOG_MIN)
				x = Joystick.ANALOG_MIN;
			if (x > Joystick.ANALOG_MAX)
				x = Joystick.ANALOG_MAX;
			if (y < Joystick.ANALOG_MIN)
				y = Joystick.ANALOG_MIN;
			if (y > Joystick.ANALOG_MAX)
				y = Joystick.ANALOG_MAX;
			
			/*System.out.println("X: "+ x);
			System.out.println("X acelerometro: "+ event.getY());
			System.out.println("X entero: "+ (int) x);
			System.out.println("Y: "+ y);
			System.out.println("Y acelerometro: "+ event.getX());		
			System.out.println("Y entero: "+ (int) y);
			*/
			joystick.analog[Joystick.ANALOG_AXIS_X] = (int)x;
			joystick.analog[Joystick.ANALOG_AXIS_Y] = (int)y;
		
		} else if (modo.equals("wheel")) {
			
			double angle = Math.atan2(x, y) * 180 / Math.PI;

			System.out.print("Angulo Orig: " + angle + " - ");
			
			if (angle < -anguloLimite)
				angle = -anguloLimite;
			if (angle > anguloLimite)
				angle = anguloLimite;
			
			if (angle >= anguloLimite && anguloAnterior <= -anguloLimite)
				angle = -anguloLimite;
			if (angle <= -anguloLimite && anguloAnterior >= anguloLimite)
				angle = anguloLimite;
			
			System.out.println("Angulo: " + angle);
			
			anguloAnterior = angle;
			
			angle = angle * factorAngular + Joystick.ANALOG_MID;
			
			if (angle < Joystick.ANALOG_MIN)
				angle = Joystick.ANALOG_MIN;
			if (angle > Joystick.ANALOG_MAX)
				angle = Joystick.ANALOG_MAX;
						
			joystick.analog[Joystick.ANALOG_AXIS_X] = (int) angle;
		}
		
		/*try {
			joystick.send();
		} catch (JoystickException e) {
			e.printStackTrace();
		}*/
	}

	@Override
	public void onCustomDataReceived(AndroidDevice device, String text, Serializable object) {
		if(text.equals("modo")){
			joystick.resetButtons();
			if(((String)object).equals("gamepad")){
				modo = "gamepad";
				try {
					device.getAccelerometer().addAccelerometerListener(this);
					button1.addOnTouchListener(this);
					button2.addOnTouchListener(this);
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else if (((String)object).equals("wheel")) {
				modo = "wheel";
				try {
					device.getAccelerometer().addAccelerometerListener(this);
					acelerador.addOnTouchListener(this);
					freno.addOnTouchListener(this);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
		}
		else if(text.equals("volver a") && ((String)object).equals("modo")){
			try {
				device.getAccelerometer().removeAccelerometerListener(this);
				for(View v : device.getTouchScreen().getViews()){
					if(v.getName().equals("button1") || v.getName().equals("button2") || v.getName().equals("acelerador") || v.getName().equals("freno"))
						v.removeOnTouchListener(this);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	public void setUpdateInterval(int updateInterval) {
		this.updateInterval = updateInterval;
	}

}
